package main

import (
    "errors"
    "fmt"
    "strconv"
)

// --- Go 的错误处理哲学 ---
// Go 语言通过返回值来传递错误，而不是通过异常。
// 函数可能会返回一个 error 类型的值作为其最后一个返回值。
// 如果 error 为 nil，表示没有发生错误；否则，error 中包含了错误信息。

// --- 标准库中的错误处理 ---
// strconv.Atoi 函数尝试将字符串转换为整数，如果失败，会返回一个非 nil 的 error。

func parseAge(s string) {
    fmt.Printf("\n尝试解析 '%s':\n", s)
    age, err := strconv.Atoi(s)
    if err != nil {
        // 处理错误
        fmt.Println("解析失败:", err)
        return
    }
    // 如果 err 为 nil，说明操作成功
    fmt.Printf("成功解析，年龄是: %d\n", age)
}

// --- 创建自定义错误 ---
// 可以使用 errors.New 或 fmt.Errorf 创建自定义错误。

// 使用 errors.New 创建一个简单的错误
var ErrInvalidInput = errors.New("输入无效")

func validateInput(input string) error {
    if input == "" {
        // 返回一个预定义的错误
        return ErrInvalidInput
    }
    if len(input) > 10 {
        // 使用 fmt.Errorf 创建一个带格式的错误信息
        return fmt.Errorf("输入 '%s' 长度超过10个字符", input)
    }
    return nil // 返回 nil 表示没有错误
}

// --- 自定义错误类型 ---
// 通过实现 Error() string 方法，可以创建自定义的错误类型。
// 这能携带更多的上下文信息。

type MyCustomError struct {
    Code    int
    Message string
}

// MyCustomError 实现了 error 接口的 Error() 方法
func (e MyCustomError) Error() string {
    return fmt.Sprintf("错误代码: %d, 消息: %s", e.Code, e.Message)
}

func doSomethingThatFails(shouldFail bool) error {
    if shouldFail {
        return MyCustomError{
            Code:    500,
            Message: "操作失败，发生了内部错误",
        }
    }
    return nil
}

// --- 错误包装和展开 ---
// Go 1.13 引入了错误包装（wrapping）和展开（unwrapping）的功能。
// 使用 %w 动词可以包装一个错误，保留原始错误信息。

func readFile(filename string) error {
    if filename == "" {
        return fmt.Errorf("读取文件失败: %w", ErrInvalidInput)
    }
    // 假设这里还有其他逻辑...
    return nil
}

// --- 使用 defer 和 recover ---
// 虽然 Go 推荐使用 error 返回值，但有时也需要处理 panic（运行时错误）。
// defer 用于确保函数退出前执行某些代码。
// recover 用于捕获 panic，防止程序崩溃。

func mayPanic(shouldPanic bool) (result string) {
    // defer 语句会在函数返回前执行，无论函数是正常返回还是 panic
    // 常用于资源清理，如关闭文件、数据库连接等
    defer func() {
        // recover() 用于捕获 panic
        // 如果没有发生 panic，recover() 返回 nil
        if p := recover(); p != nil {
            // 捕获到 panic，可以进行处理
            result = fmt.Sprintf("从 panic 中恢复: %v", p)
            fmt.Println("在 defer 中捕获到 panic:", p)
        }
    }()

    if shouldPanic {
        // panic 会中断程序的正常执行流程
        panic("这是一个故意的 panic!")
    }

    return "函数正常执行完毕"
}


func main() {
    // --- 标准错误处理 ---
    fmt.Println("--- 标准错误处理 ---")
    parseAge("25")
    parseAge("abc")

    // --- 自定义错误 ---
    fmt.Println("\n--- 自定义错误 ---")
    inputs := []string{"", "短", "这是一个非常非常长的输入"}
    for _, input := range inputs {
        err := validateInput(input)
        if err != nil {
            fmt.Println("验证失败:", err)
            // 使用 errors.Is 检查错误是否是某个特定的错误
            if errors.Is(err, ErrInvalidInput) {
                fmt.Println("  -> 这是 ErrInvalidInput 错误")
            }
        } else {
            fmt.Printf("'%s' 验证通过\n", input)
        }
    }

    // --- 自定义错误类型 ---
    fmt.Println("\n--- 自定义错误类型 ---")
    err := doSomethingThatFails(true)
    if err != nil {
        fmt.Println("操作失败:", err)
        // 使用类型断言来获取自定义错误的详细信息
        if customErr, ok := err.(MyCustomError); ok {
            fmt.Printf("  -> 错误代码: %d\n", customErr.Code)
        }
    }

    // --- 错误包装 ---
    fmt.Println("\n--- 错误包装 ---")
    err = readFile("")
    if err != nil {
        fmt.Println("发生错误:", err)
        // 使用 errors.Unwrap 获取被包装的原始错误
        if unwrappedErr := errors.Unwrap(err); unwrappedErr != nil {
            fmt.Println("  -> 内部错误是:", unwrappedErr)
        }
        // 使用 errors.Is 检查链中是否包含某个特定错误
        if errors.Is(err, ErrInvalidInput) {
            fmt.Println("  -> 错误链中包含 ErrInvalidInput")
        }
    }

    // --- defer 和 recover ---
    fmt.Println("\n--- defer 和 recover ---")
    result1 := mayPanic(false)
    fmt.Println("结果:", result1)

    result2 := mayPanic(true)
    fmt.Println("结果:", result2)
    fmt.Println("程序继续执行，没有崩溃")
}